//*****************************************************************************
// JFab (http://code.google.com/p/jfab)
// Copyright (c) 2011-2012 JFab.org
// Admin jfab@jeffreythompson.net
//
// See the file "LICENSE.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//*****************************************************************************

package org.jfab.core.inflator;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.jfab.core.collection.CGAnnotationList;
import org.jfab.core.collection.CGConstructorList;
import org.jfab.core.collection.CGFieldList;
import org.jfab.core.collection.CGMethodList;
import org.jfab.core.collection.CGParameterList;
import org.jfab.core.collection.CGTypeList;
import org.jfab.core.collection.CommentList;
import org.jfab.core.collection.MethodBodyList;
import org.jfab.core.collection.ModifierList;
import org.jfab.core.factory.CGConstructorFactory;
import org.jfab.core.factory.CGFieldFactory;
import org.jfab.core.factory.CGMethodFactory;
import org.jfab.core.factory.CGParameterFactory;
import org.jfab.core.factory.CGTypeFactory;
import org.jfab.core.factory.StringFactory;
import org.jfab.core.model.CGCompilationUnit;
import org.jfab.core.model.CGConcreteUnit;
import org.jfab.core.model.CGConstructor;
import org.jfab.core.model.CGField;
import org.jfab.core.model.CGMethod;
import org.jfab.core.model.CGParameter;
import org.jfab.core.model.CGType;
import org.jfab.core.model.CGTypeUtilities;
import org.jfab.core.model.Modifier;

/**
 * Provides a partial implementation of a code generator concrete unit inflator.
 * 
 * @param <T> Code generator concrete unit type.
 */
public abstract class CGConcreteUnitInflator<T extends CGConcreteUnit> extends
        CGCompilationUnitInflator<T>
{
    /** Constructor comparator. */
    private final Comparator<CGConstructor> _constructorComparator;

    /** Constructor factory. */
    private final CGConstructorFactory _constructorFactory;

    /** Method comparator. */
    private final Comparator<CGMethod> _methodComparator;

    /** Method factory. */
    private final CGMethodFactory _methodFactory;

    /** Parameter factory. */
    private final CGParameterFactory _parameterFactory;

    /**
     * Construct this object with the given parameters.
     * 
     * @param stringFactory String factory. (required)
     * @param cgTypeUtils Code generator type utilities. (required)
     * @param cgTypeFactory Code generator type factory. (required)
     * @param fieldFactory Field factory. (required)
     * @param fieldComparator Field comparator. (required)
     * 
     * @param parameterFactory Parameter factory. (required)
     * @param constructorFactory Constructor factory. (required)
     * @param constructorComparator Constructor comparator. (required)
     * @param methodFactory Method factory. (required)
     * @param methodComparator Method comparator. (required)
     */
    public CGConcreteUnitInflator(final StringFactory stringFactory,
            final CGTypeUtilities cgTypeUtils,
            final CGTypeFactory cgTypeFactory,
            final CGFieldFactory fieldFactory,
            final Comparator<CGField> fieldComparator,
            final CGParameterFactory parameterFactory,
            final CGConstructorFactory constructorFactory,
            final Comparator<CGConstructor> constructorComparator,
            final CGMethodFactory methodFactory,
            final Comparator<CGMethod> methodComparator)
    {
        super(stringFactory, cgTypeUtils, cgTypeFactory, fieldFactory,
                fieldComparator);

        Validate.notNull(parameterFactory, "parameterFactory is null");
        Validate.notNull(constructorFactory, "constructorFactory is null");
        Validate.notNull(constructorComparator, "constructorComparator is null");
        Validate.notNull(methodFactory, "methodFactory is null");
        Validate.notNull(methodComparator, "methodComparator is null");

        _parameterFactory = parameterFactory;
        _constructorFactory = constructorFactory;
        _constructorComparator = constructorComparator;
        _methodFactory = methodFactory;
        _methodComparator = methodComparator;
    }

    /**
     * @param modifiers Modifiers.
     * @param cgConcreteUnit Code generator concrete unit.
     * @param inflatedFields Inflated fields.
     * 
     * @return new constructors created from the given parameter.
     */
    protected CGConstructorList createConstructors(
            final ModifierList modifiers, final CGConcreteUnit cgConcreteUnit,
            final CGFieldList inflatedFields)
    {
        final CGConstructorList answer = new CGConstructorList();

        if (CollectionUtils.isNotEmpty(cgConcreteUnit.getConstructors()))
        {
            answer.addAll(cgConcreteUnit.getConstructors());
        }

        final CGConstructor constructor = createConstructorForFinalFields(
                modifiers, cgConcreteUnit, inflatedFields);

        if (constructor != null)
        {
            answer.add(constructor);
        }

        Collections.sort(answer, getConstructorComparator());

        return answer;
    }

    /**
     * @param methodList Method list.
     * @param cgConcreteUnit Code generator concrete unit.
     * @param inflatedFields Inflated fields.
     */
    protected void createMethods(final CGMethodList methodList,
            final CGConcreteUnit cgConcreteUnit,
            final CGFieldList inflatedFields)
    {
        if (cgConcreteUnit.isPropertyChangeSupported())
        {
            createPropertyChangeMethods(methodList);
        }

        if (CollectionUtils.isNotEmpty(cgConcreteUnit.getMethods()))
        {
            methodList.addAll(cgConcreteUnit.getMethods());
        }

        createAccessorMutatorMethods(methodList, cgConcreteUnit, inflatedFields);

        Collections.sort(methodList, getMethodComparator());
    }

    /**
     * @return the constructorComparator
     */
    protected Comparator<CGConstructor> getConstructorComparator()
    {
        return _constructorComparator;
    }

    /**
     * @return the constructorFactory
     */
    protected CGConstructorFactory getConstructorFactory()
    {
        return _constructorFactory;
    }

    /**
     * @return the methodComparator
     */
    protected Comparator<CGMethod> getMethodComparator()
    {
        return _methodComparator;
    }

    /**
     * @return the methodFactory
     */
    protected CGMethodFactory getMethodFactory()
    {
        return _methodFactory;
    }

    /**
     * @return the parameterFactory
     */
    protected CGParameterFactory getParameterFactory()
    {
        return _parameterFactory;
    }

    /**
     * @param methodList Method list.
     * @param cgConcreteUnit Code generator concrete unit.
     * @param inflatedFields Inflated fields.
     */
    private void createAccessorMutatorMethods(final CGMethodList methodList,
            final CGConcreteUnit cgConcreteUnit,
            final CGFieldList inflatedFields)
    {
        if (CollectionUtils.isNotEmpty(inflatedFields))
        {
            final CGMethodFactory methodFactory = getMethodFactory();

            for (final CGField field : inflatedFields)
            {
                if (isGetter(field))
                {
                    final CGMethod method = methodFactory
                            .createAccessorMethod(field);
                    if ((method != null)
                            && (methodList.findByName(method.getName()) == null))
                    {
                        methodList.add(method);
                    }
                }

                if (isSetter(field))
                {
                    if (cgConcreteUnit.isPropertyChangeSupported())
                    {
                        final CGMethod method = methodFactory
                                .createMutatorPropertyChangeMethod(field);
                        if ((method != null)
                                && (methodList.findByName(method.getName()) == null))
                        {
                            methodList.add(method);
                        }
                    }
                    else
                    {
                        final CGMethod method = methodFactory
                                .createMutatorMethod(field);
                        if ((method != null)
                                && (methodList.findByName(method.getName()) == null))
                        {
                            methodList.add(method);
                        }
                    }
                }
            }
        }
    }

    /**
     * @param modifiers Modifiers.
     * @param cgCompilationUnit Code generator compilation unit.
     * @param inflatedFields Inflated fields.
     * 
     * @return new code generator constructors.
     */
    private CGConstructor createConstructorForFinalFields(
            final ModifierList modifiers,
            final CGCompilationUnit cgCompilationUnit,
            final List<CGField> inflatedFields)
    {
        final String className = cgCompilationUnit.getName();
        final CGFieldList fields = new CGFieldList(inflatedFields);

        return createConstructorForFinalFields(modifiers, className, fields);
    }

    /**
     * @param modifiers Modifiers.
     * @param className Class name.
     * @param fields Fields.
     * 
     * @return new code generator constructors.
     */
    private CGConstructor createConstructorForFinalFields(
            final ModifierList modifiers, final String className,
            final CGFieldList fields)
    {
        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList();
        final CGAnnotationList annotations = null;
        final CGParameterList parameters = new CGParameterList();
        final CGTypeList throwsTypes = null;
        final MethodBodyList methodBodyLines = new MethodBodyList();

        final CGParameterFactory parameterFactory = getParameterFactory();
        final StringFactory stringFactory = getStringFactory();
        commentLines.add("Construct this object.");

        for (final CGField field : fields)
        {
            if ((field.getModifiers() != null)
                    && field.getModifiers().contains(Modifier.FINAL.getName())
                    && StringUtils.isEmpty(field.getInitialValue()))
            {
                final String parameterName = stringFactory
                        .createPropertyName(field.getName());
                commentLines.add("@param "
                        + parameterName
                        + " "
                        + stringFactory
                                .createCommentFromPropertyName(parameterName));

                final CGType parameterType = field.getType();
                final CGParameter parameter = parameterFactory.createParameter(
                        parameterType, parameterName);
                parameters.add(parameter);

                final String fieldName = field.getName();
                methodBodyLines.add(fieldName + " = " + parameterName + ";");
            }
        }

        if (methodBodyLines.isEmpty())
        {
            methodBodyLines.add("// Nothing to do.");
        }

        CGConstructor answer = null;

        if (!parameters.isEmpty())
        {
            final CGConstructorFactory constructorFactory = getConstructorFactory();
            answer = constructorFactory.createConstructor(referencedTypes,
                    commentLines, annotations, modifiers, className,
                    parameters, throwsTypes, methodBodyLines);
        }

        return answer;
    }

    /**
     * @param methodList Method list.
     */
    private void createPropertyChangeMethods(final CGMethodList methodList)
    {
        final CGMethodFactory methodFactory = getMethodFactory();
        final CGAnnotationList annotations = null;
        final ModifierList mutatorModifiers = new ModifierList(Modifier.PUBLIC);

        methodList.add(methodFactory.createAddPropertyChangeListenerMethod(
                annotations, mutatorModifiers));
        methodList.add(methodFactory
                .createAddPropertyChangeListenerNamedMethod(annotations,
                        mutatorModifiers));
        methodList.add(methodFactory.createRemovePropertyChangeListenerMethod(
                annotations, mutatorModifiers));
        methodList.add(methodFactory
                .createRemovePropertyChangeListenerNamedMethod(annotations,
                        mutatorModifiers));
        methodList.add(methodFactory.createGetPropertyChangeSupportMethod());
    }
}
